package com.liujit.gateway.security;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import com.liujit.common.constants.AuthConstant;
import com.liujit.common.constants.GlobalConstant;
import com.liujit.common.constants.HeaderConstant;
import com.liujit.common.domain.result.Result;
import com.liujit.gateway.security.properties.SecurityProperties;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.server.authorization.AuthorizationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.WebClientResponseException;
import reactor.core.publisher.Mono;

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 网关自定义鉴权管理器
 *
 * @author haoxr
 * @date 2020-05-01
 */
@Slf4j
@RequiredArgsConstructor
@Component
@EnableConfigurationProperties(SecurityProperties.class)
public class ResourceServerManager implements ReactiveAuthorizationManager<AuthorizationContext> {

    private final WebClient webClient;

    private final SecurityProperties securityProperties;

    @Override
    public Mono<AuthorizationDecision> check(Mono<Authentication> authenticationMono,
            AuthorizationContext authorizationContext) {
        ServerHttpRequest request = authorizationContext.getExchange().getRequest();
        // 当前请求路径
        String path = request.getURI().getPath();
        log.info("请求路径：{}", path);
        PathMatcher pathMatcher = new AntPathMatcher();
        // APP、PC请求无需鉴权，只需认证（即JWT的验签和是否过期判断）
        if (pathMatcher.match(GlobalConstant.APP_API_PATTERN, path) || pathMatcher
                .match(GlobalConstant.PC_API_PATTERN, path)) {
            String token = request.getHeaders().getFirst(HeaderConstant.AUTHORIZATION);
            // 如果token以"Bearer "为前缀，到这一步说明是经过NimbusReactiveJwtDecoder#decode和JwtTimestampValidator#validate等解析和验证通过的，即已认证
            if (StrUtil.isNotBlank(token) && token.startsWith(AuthConstant.BEARER)) {
                return Mono.just(new AuthorizationDecision(true));
            } else {
                return Mono.just(new AuthorizationDecision(false));
            }
        }
        // 获取用户信息地址
        if (this.checkBaseUrl(path)) {
            return Mono.just(new AuthorizationDecision(true));
        }
        HttpHeaders httpHeaders = request.getHeaders();
        // 根据当前请求地址获取角色编号
        Map<String, Object> params = new HashMap<>(4);
        params.put("urlPermission", path);
        params.put("menuId", httpHeaders.getFirst(HeaderConstant.MENU_ID));
        String url = securityProperties.getUrlPermissionRole() + "?" + MapUtil.join(params, "&", "=");
        Mono<Result> rolesMono = webClient.get().uri(url).retrieve().bodyToMono(Result.class)
                .doOnError(WebClientResponseException.class, err -> {
                    log.info("根据当前请求地址获取角色编号 ERROR status:{},msg:{}", err.getRawStatusCode(),
                            err.getResponseBodyAsString());
                    throw new RuntimeException(err.getMessage() + " ;" + err.getResponseBodyAsString());
                });
        return Mono.zip(rolesMono, authenticationMono).map(tuple -> {
            // 拥有当前URL标识的身份
            Result<List<String>> t1 = tuple.getT1();
            List<String> roles = t1.getData();
            Authentication t2 = tuple.getT2();
            return new AuthorizationDecision(checkAuthorities(t2, roles));
        });
    }

    /**
     * 权限验证
     *
     * @param auth
     * @param roles
     * @return
     * @author: liujun
     * @date: 2021/6/29 9:45 上午
     */
    private boolean checkAuthorities(Authentication auth, List<String> roles) {
        if (CollectionUtil.isEmpty(roles)) {
            return false;
        }
        boolean isAuthenticated = auth.isAuthenticated();
        if (!isAuthenticated) {
            return false;
        }
        Collection<? extends GrantedAuthority> list = auth.getAuthorities();
        for (GrantedAuthority item : list) {
            String authority = item.getAuthority();
            log.info("用户权限（角色） : {}", authority);
            String role = authority.substring(AuthConstant.AUTHORITY.length());
            // 如果是超级管理员则放行
            if (GlobalConstant.SUPER_ROLE_ID.equals(role)) {
                return true;
            }
            return roles.contains(role);
        }
        return false;
    }

    /**
     * 验证当前url是否是基础操作
     *
     * @param path
     * @return
     * @author: liujun
     * @date: 2021/6/30 9:20 上午
     */
    private Boolean checkBaseUrl(String path) {
        boolean res = false;
        // Ant路径匹配模式
        PathMatcher pathMatcher = new AntPathMatcher();
        List<String> baseUrls = securityProperties.getBaseUrls();
        for (String ignoreUrl : baseUrls) {
            if (pathMatcher.match(ignoreUrl, path)) {
                res = true;
                break;
            }
        }
        return res;
    }
}
